import http from 'http';
import Context from './context';
import { Filter, FilterChain } from './utils/filter';
import { HttpError } from './utils/error';

export default class Application {
  #filters: Filter<Context>[] = [];

  listen(port: number): http.Server {
    const server = http.createServer(this.callback());
    return server.listen({ port });
  }

  use(filter: Filter<Context>): void {
    this.#filters.push(filter);
  }

  callback(): http.RequestListener {
    const handleRequest = (req: http.IncomingMessage, res: http.ServerResponse): void => {
      const ctx = this.createContext(req, res);
      this.handleRequest(ctx);
    };

    return handleRequest;
  }

  private createContext(req: http.IncomingMessage, res: http.ServerResponse): Context {
    return new Context({ res, req });
  }

  private async handleRequest(ctx: Context): Promise<void> {
    const response = ctx.response;
    response.statusCode = 404;

    const filterChain = new FilterChain<Context>();
    filterChain.addFilters(...this.#filters);
    try {
      await filterChain.next(ctx);
    } catch (err) {
      // TODO: 记录日志
      this.doFilterError(err, ctx);
    } finally {
      this.respond(ctx);
    }
  }

  private doFilterError(err: Error, ctx: Context): void {
    if (err instanceof HttpError) {
      ctx.response.statusCode = err.statusCode;
      const { message, errorCode, cause } = err;
      ctx.response.body = JSON.stringify({ msg: message, code: errorCode, cause });
    } else {
      ctx.response.statusCode = 500;
      ctx.response.body = JSON.stringify({ msg: err.message, code: 'UNHANDLED', cause: err });
    }
  }

  private respond(ctx: Context): void {
    ctx.response.res.statusCode = ctx.response.statusCode;
    ctx.response.res.end(ctx.response.body);
  }
}
